package com.facebook.react.bridge;

import java.lang.reflect.Array;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import android.os.Bundle;

public class Argument {
    /**
     *  将 java 类转给js 调用
     */
    private static Object makeNativeObject(Object object) {
        if (object == null) {
            return null;
        } else if (object instanceof Byte) {
            return new Integer(((Number) object).byteValue());
        } else if (object instanceof Short) {
            return new Integer(((Number) object).shortValue());
        } else if (object instanceof Integer) {
            return new Integer(((Number) object).intValue());
        } else if (object instanceof Float) {
            return new Double(((Number) object).floatValue());
        } else if (object instanceof Long) {
            return new Double(((Number) object).longValue());
        } else if (object instanceof Double) {
            return new Double(((Number) object).doubleValue());
        } else if (object.getClass().isArray()) {
            return makeNativeArray(object);
        } else if (object instanceof List) {
            return makeNativeArray((List) object);
        } else if (object instanceof Map) {
            return makeNativeMap((Map<String, Object>) object);
        } else if (object instanceof Bundle) {
            return makeNativeMap((Bundle) object);
        } else {
            return object;
        }
    }
    /**
     *  将 java 类转给js 调用
     */
    public static WritableNativeArray makeNativeArray(List objects) {
        WritableNativeArray nativeArray = new WritableNativeArray();
        if (objects == null) {
            return nativeArray;
        }
        for (Object elem : objects) {
            elem = makeNativeObject(elem);
            if (elem == null) {
                nativeArray.pushNull();
            } else if (elem instanceof Boolean) {
                nativeArray.pushBoolean((Boolean) elem);
            } else if (elem instanceof Integer) {
                nativeArray.pushInt((Integer) elem);
            } else if (elem instanceof Double) {
                nativeArray.pushDouble((Double) elem);
            } else if (elem instanceof String) {
                nativeArray.pushString((String) elem);
            } else if (elem instanceof WritableNativeArray) {
                nativeArray.pushArray((WritableNativeArray) elem);
            } else if (elem instanceof WritableNativeMap) {
                nativeArray.pushMap((WritableNativeMap) elem);
            } else {
                throw new IllegalArgumentException("Could not convert " + elem.getClass());
            }
        }
        return nativeArray;
    }

    /**
     *  将 java 类转给js 调用
     */
    public static <T> WritableNativeArray makeNativeArray(final Object objects) {
        if (objects == null) {
            return new WritableNativeArray();
        }
        return makeNativeArray(new AbstractList() {
            public int size() {
                return Array.getLength(objects);
            }

            public Object get(int index) {
                return Array.get(objects, index);
            }
        });
    }

    private static void addEntry(WritableNativeMap nativeMap, String key, Object value) {
        value = makeNativeObject(value);
        if (value == null) {
            nativeMap.putNull(key);
        } else if (value instanceof Boolean) {
            nativeMap.putBoolean(key, (Boolean) value);
        } else if (value instanceof Byte) {
            nativeMap.putInt(key, (Byte) value);
        } else if ( value instanceof Short ) {
            nativeMap.putInt(key, (Short) value);
        } else if (value instanceof Integer) {
            nativeMap.putInt(key, (Integer) value);
        } else if (value instanceof Float) {
            nativeMap.putDouble(key, ((Float) value).floatValue());
        } else if ( value instanceof Long) {
            nativeMap.putDouble(key, ((Long) value).longValue());
        } else if (value instanceof Double) {
            nativeMap.putDouble(key, ((Double) value).doubleValue());
        } else if (value instanceof String) {
            nativeMap.putString(key, (String) value);
        } else if (value instanceof WritableNativeArray) {
            nativeMap.putArray(key, (WritableNativeArray) value);
        } else if (value instanceof WritableNativeMap) {
            nativeMap.putMap(key, (WritableNativeMap) value);
        } else {
            throw new IllegalArgumentException("Could not convert " + value.getClass());
        }
    }

    /**
     *  将 java 类转给js 调用
     */
    public static WritableNativeMap makeNativeMap(Map<String, Object> objects) {
        WritableNativeMap nativeMap = new WritableNativeMap();
        if (objects == null) {
            return nativeMap;
        }
        for (Map.Entry<String, Object> entry : objects.entrySet()) {
            addEntry(nativeMap, entry.getKey(), entry.getValue());
        }
        return nativeMap;
    }

    /**
     *  将 java 类转给js 调用
     */
    public static WritableNativeMap makeNativeMap(Bundle bundle) {
        WritableNativeMap nativeMap = new WritableNativeMap();
        if (bundle == null) {
            return nativeMap;
        }
        for (String key : bundle.keySet()) {
            addEntry(nativeMap, key, bundle.get(key));
        }
        return nativeMap;
    }

    public static WritableArray createArray() {
        return new WritableNativeArray();
    }
    public static WritableMap createMap() {
        return new WritableNativeMap();
    }

    public static WritableNativeArray fromJavaArgs(Object[] args) {
        WritableNativeArray arguments = new WritableNativeArray();
        for (int i = 0; i < args.length; i++) {
            Object argument = args[i];
            if (argument == null) {
                arguments.pushNull();
                continue;
            }

            Class argumentClass = argument.getClass();
            if (argumentClass == Boolean.class) {
                arguments.pushBoolean(((Boolean) argument).booleanValue());
            } else if (argumentClass == Byte.class) {
                arguments.pushInt(((Number) argument).byteValue());
            } else if (argumentClass == Short.class) {
                arguments.pushInt(((Number) argument).shortValue());
            } else if (argumentClass == Integer.class) {
                arguments.pushInt(((Number) argument).intValue());
            } else if (argumentClass == Float.class) {
                arguments.pushDouble(((Float) argument).floatValue());
            } else if (argumentClass == Long.class) {
                arguments.pushDouble(((Long) argument).longValue());
            } else if (argumentClass == Double.class) {
                arguments.pushDouble(((Double) argument).doubleValue());
            } else if (argumentClass == String.class) {
                arguments.pushString(argument.toString());
            } else if (argumentClass == WritableNativeMap.class) {
                arguments.pushMap((WritableNativeMap) argument);
            } else if (argumentClass == WritableNativeArray.class) {
                arguments.pushArray((WritableNativeArray) argument);
            } else {
                throw new RuntimeException("Cannot convert argument of type " + argumentClass);
            }
        }
        return arguments;
    }

    public static WritableArray fromArray(Object array) {
        WritableArray catalystArray = createArray();
        if (array instanceof String[]) {
            for (String v : (String[]) array) {
                catalystArray.pushString(v);
            }
        } else if (array instanceof Bundle[]) {
            for (Bundle v : (Bundle[]) array) {
                catalystArray.pushMap(fromBundle(v));
            }
        } else if (array instanceof int[]) {
            for (int v : (int[]) array) {
                catalystArray.pushInt(v);
            }
        } else if (array instanceof short[]) {
            for (short v : (short[]) array) {
                catalystArray.pushInt(v);
            }
        } else if (array instanceof long[]) {
            for (long v : (long[]) array) {
                catalystArray.pushDouble(v);
            }
        } else if (array instanceof float[]) {
            for (float v : (float[]) array) {
                catalystArray.pushDouble(v);
            }
        } else if (array instanceof double[]) {
            for (double v : (double[]) array) {
                catalystArray.pushDouble(v);
            }
        } else if (array instanceof boolean[]) {
            for (boolean v : (boolean[]) array) {
                catalystArray.pushBoolean(v);
            }
        } else {
            throw new IllegalArgumentException("Unknown array type " + array.getClass());
        }
        return catalystArray;
    }
    public static WritableArray fromList(List list) {
        WritableArray catalystArray = createArray();
        for (Object obj : list) {
            if (obj == null) {
                catalystArray.pushNull();
            } else if (obj.getClass().isArray()) {
                catalystArray.pushArray(fromArray(obj));
            } else if (obj instanceof Bundle) {
                catalystArray.pushMap(fromBundle((Bundle) obj));
            } else if (obj instanceof List) {
                catalystArray.pushArray(fromList((List) obj));
            } else if (obj instanceof String) {
                catalystArray.pushString((String) obj);
            } else if (obj instanceof Number) {
                if (obj instanceof Short) {
                    catalystArray.pushInt((Short) obj);
                } else if (obj instanceof Integer) {
                    catalystArray.pushInt((Integer) obj);
                } else if (obj instanceof Long) {
                    catalystArray.pushDouble(((Long) obj).longValue());
                } else if (obj instanceof Float) {
                    catalystArray.pushDouble(((Float) obj).floatValue());
                } else if (obj instanceof Double) {
                    catalystArray.pushDouble(((Double) obj).doubleValue());
                }
            } else if (obj instanceof Boolean) {
                catalystArray.pushBoolean((Boolean) obj);
            } else {
                throw new IllegalArgumentException("Unknown value type " + obj.getClass());
            }
        }
        return catalystArray;
    }

    public static WritableMap fromBundle(Bundle bundle) {
        WritableMap map = createMap();
        for (String key : bundle.keySet()) {
            Object value = bundle.get(key);
            if (value == null) {
                map.putNull(key);
            } else if (value.getClass().isArray()) {
                map.putArray(key, fromArray(value));
            } else if (value instanceof String) {
                map.putString(key, (String) value);
            } else if (value instanceof Number) {
                if (value instanceof Short) {
                    map.putInt(key,(Short) value);
                } else if (value instanceof Integer) {
                    map.putInt(key,(Integer) value);
                } else if (value instanceof Long) {
                    map.putDouble(key,((Long) value).longValue());
                } else if (value instanceof Float) {
                    map.putDouble(key,((Float) value).floatValue());
                } else if (value instanceof Double) {
                    map.putDouble(key,((Double) value).doubleValue());
                }
            } else if (value instanceof Boolean) {
                map.putBoolean(key, (Boolean) value);
            } else if (value instanceof Bundle) {
                map.putMap(key, fromBundle((Bundle) value));
            } else if (value instanceof List) {
                map.putArray(key, fromList((List) value));
            } else {
                throw new IllegalArgumentException("Could not convert " + value.getClass());
            }
        }
        return map;
    }

    //==========================================================================
    @Nullable
    public static ArrayList toList(@Nullable ReadableArray readableArray) {
        ArrayList list = new ArrayList();
        if (readableArray == null) {
            return null;
        }
        for (int i = 0; i < readableArray.size(); i++) {
            switch (readableArray.getType(i)) {
                case Null:
                    list.add(null);
                    break;
                case Boolean:
                    list.add(readableArray.getBoolean(i));
                    break;
                case Number:
                    double number = readableArray.getDouble(i);
                    if (number == Math.rint(number)) {
                        if(Integer.MIN_VALUE<number&&number<Integer.MAX_VALUE){
                            list.add((int)number);
                        }else{
                            list.add((long)number);
                        }
                    }else{
                        list.add(number);
                    }
                    break;
                case String:
                    list.add(readableArray.getString(i));
                    break;
                case Map:
                    list.add(toBundle(readableArray.getMap(i)));
                    break;
                case Array:
                    list.add(toList(readableArray.getArray(i)));
                    break;
                default:
                    throw new IllegalArgumentException("Could not convert object in array.");
            }
        }
        return list;
    }

    public static Map toMap(@Nullable ReadableMap readableMap){
        Map map = new HashMap();
        if (readableMap == null) {
            return map;
        }
        ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
        while (iterator.hasNextKey()) {
            String key = iterator.nextKey();
            ReadableType readableType = readableMap.getType(key);
            switch (readableType) {
                case Null:
                    map.put(key, null);
                    break;
                case Boolean:
                    map.put(key, readableMap.getBoolean(key));
                    break;
                case Number:
                    double number = readableMap.getDouble(key);
                    if (number == Math.rint(number)) {
                        if(Integer.MIN_VALUE<number&&number<Integer.MAX_VALUE){
                            map.put(key, (int)number);
                        }else{
                            map.put(key, (long)number);
                        }
                    }else{
                        map.put(key,number);
                    }
                    break;
                case String:
                    map.put(key, readableMap.getString(key));
                    break;
                case Map:
                    map.put(key, toBundle(readableMap.getMap(key)));// 只有外层转成map 了
                    break;
                case Array:
                    map.put(key, toList(readableMap.getArray(key)));// 只有外层转成map 了
                    break;
                default:
                    throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
            }
        }
        return map;
    }
    @Nullable
    public static Bundle toBundle(@Nullable ReadableMap readableMap) {
        if (readableMap == null) {
            return null;
        }
        ReadableMapKeySetIterator iterator = readableMap.keySetIterator();

        Bundle bundle = new Bundle();
        while (iterator.hasNextKey()) {
            String key = iterator.nextKey();
            ReadableType readableType = readableMap.getType(key);
            switch (readableType) {
                case Null:
                    bundle.putString(key, null);
                    break;
                case Boolean:
                    bundle.putBoolean(key, readableMap.getBoolean(key));
                    break;
                case Number:
                    double number = readableMap.getDouble(key);
                    if (number == Math.rint(number)) {
                        if(Integer.MIN_VALUE<number&&number<Integer.MAX_VALUE){
                            bundle.putInt(key, (int)number);
                        }else{
                            bundle.putLong(key, (long)number);
                        }
                    }else{
                        bundle.putDouble(key,number);
                    }
                    break;
                case String:
                    bundle.putString(key, readableMap.getString(key));
                    break;
                case Map:
                    bundle.putBundle(key, toBundle(readableMap.getMap(key)));
                    break;
                case Array:
                    bundle.putSerializable(key, toList(readableMap.getArray(key)));
                    break;
                default:
                    throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
            }
        }
        return bundle;
    }



}
